home *** CD-ROM | disk | FTP | other *** search
- // Copyright (c) 2012 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
-
- // // Copyright (c) 2013 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
-
- /**
- * @fileoverview Assertion support.
- */
-
- /**
- * Verify |condition| is truthy and return |condition| if so.
- * @template T
- * @param {T} condition A condition to check for truthiness. Note that this
- * may be used to test whether a value is defined or not, and we don't want
- * to force a cast to Boolean.
- * @param {string=} opt_message A message to show on failure.
- * @return {T} A non-null |condition|.
- */
- function assert(condition, opt_message) {
- 'use strict';
- if (!condition) {
- var msg = 'Assertion failed';
- if (opt_message)
- msg = msg + ': ' + opt_message;
- throw new Error(msg);
- }
- return condition;
- }
-
- /**
- * Call this from places in the code that should never be reached.
- *
- * For example, handling all the values of enum with a switch() like this:
- *
- * function getValueFromEnum(enum) {
- * switch (enum) {
- * case ENUM_FIRST_OF_TWO:
- * return first
- * case ENUM_LAST_OF_TWO:
- * return last;
- * }
- * assertNotReached();
- * return document;
- * }
- *
- * This code should only be hit in the case of serious programmer error or
- * unexpected input.
- *
- * @param {string=} opt_message A message to show when this is hit.
- */
- function assertNotReached(opt_message) {
- throw new Error(opt_message || 'Unreachable code hit');
- }
-
- /**
- * @param {*} value The value to check.
- * @param {function(new: T, ...)} type A user-defined constructor.
- * @param {string=} opt_message A message to show when this is hit.
- * @return {T}
- * @template T
- */
- function assertInstanceof(value, type, opt_message) {
- if (!(value instanceof type)) {
- throw new Error(opt_message ||
- value + ' is not a[n] ' + (type.name || typeof type));
- }
- return value;
- }
-
-
- /**
- * Alias for document.getElementById.
- * @param {string} id The ID of the element to find.
- * @return {HTMLElement} The found element or null if not found.
- */
- function $(id) {
- return document.getElementById(id);
- }
-
- /**
- * Add an accessible message to the page that will be announced to
- * users who have spoken feedback on, but will be invisible to all
- * other users. It's removed right away so it doesn't clutter the DOM.
- * @param {string} msg The text to be pronounced.
- */
- function announceAccessibleMessage(msg) {
- var element = document.createElement('div');
- element.setAttribute('aria-live', 'polite');
- element.style.position = 'relative';
- element.style.left = '-9999px';
- element.style.height = '0px';
- element.innerText = msg;
- document.body.appendChild(element);
- window.setTimeout(function() {
- document.body.removeChild(element);
- }, 0);
- }
-
- /**
- * Calls chrome.send with a callback and restores the original afterwards.
- * @param {string} name The name of the message to send.
- * @param {!Array} params The parameters to send.
- * @param {string} callbackName The name of the function that the backend calls.
- * @param {!Function} callback The function to call.
- */
- function chromeSend(name, params, callbackName, callback) {
- var old = global[callbackName];
- global[callbackName] = function() {
- // restore
- global[callbackName] = old;
-
- var args = Array.prototype.slice.call(arguments);
- return callback.apply(global, args);
- };
- chrome.send(name, params);
- }
-
- /**
- * Returns the scale factors supported by this platform.
- * @return {Array} The supported scale factors.
- */
- function getSupportedScaleFactors() {
- var supportedScaleFactors = [];
- if (cr.isMac || cr.isChromeOS) {
- supportedScaleFactors.push(1);
- supportedScaleFactors.push(2);
- } else {
- // Windows must be restarted to display at a different scale factor.
- supportedScaleFactors.push(window.devicePixelRatio);
- }
- return supportedScaleFactors;
- }
-
- /**
- * Generates a CSS url string.
- * @param {string} s The URL to generate the CSS url for.
- * @return {string} The CSS url string.
- */
- function url(s) {
- // http://www.w3.org/TR/css3-values/#uris
- // Parentheses, commas, whitespace characters, single quotes (') and double
- // quotes (") appearing in a URI must be escaped with a backslash
- var s2 = s.replace(/(\(|\)|\,|\s|\'|\"|\\)/g, '\\$1');
- // WebKit has a bug when it comes to URLs that end with \
- // https://bugs.webkit.org/show_bug.cgi?id=28885
- if (/\\\\$/.test(s2)) {
- // Add a space to work around the WebKit bug.
- s2 += ' ';
- }
- return 'url("' + s2 + '")';
- }
-
- /**
- * Returns the URL of the image, or an image set of URLs for the profile avatar.
- * Default avatars have resources available for multiple scalefactors, whereas
- * the GAIA profile image only comes in one size.
- *
- * @param {string} path The path of the image.
- * @return {string} The url, or an image set of URLs of the avatar image.
- */
- function getProfileAvatarIcon(path) {
- var chromeThemePath = 'chrome://theme';
- var isDefaultAvatar =
- (path.slice(0, chromeThemePath.length) == chromeThemePath);
- return isDefaultAvatar ? imageset(path + '@scalefactorx'): url(path);
- }
-
- /**
- * Generates a CSS -webkit-image-set for a chrome:// url.
- * An entry in the image set is added for each of getSupportedScaleFactors().
- * The scale-factor-specific url is generated by replacing the first instance of
- * 'scalefactor' in |path| with the numeric scale factor.
- * @param {string} path The URL to generate an image set for.
- * 'scalefactor' should be a substring of |path|.
- * @return {string} The CSS -webkit-image-set.
- */
- function imageset(path) {
- var supportedScaleFactors = getSupportedScaleFactors();
-
- var replaceStartIndex = path.indexOf('scalefactor');
- if (replaceStartIndex < 0)
- return url(path);
-
- var s = '';
- for (var i = 0; i < supportedScaleFactors.length; ++i) {
- var scaleFactor = supportedScaleFactors[i];
- var pathWithScaleFactor = path.substr(0, replaceStartIndex) + scaleFactor +
- path.substr(replaceStartIndex + 'scalefactor'.length);
-
- s += url(pathWithScaleFactor) + ' ' + scaleFactor + 'x';
-
- if (i != supportedScaleFactors.length - 1)
- s += ', ';
- }
- return '-webkit-image-set(' + s + ')';
- }
-
- /**
- * Parses query parameters from Location.
- * @param {Location} location The URL to generate the CSS url for.
- * @return {Object} Dictionary containing name value pairs for URL
- */
- function parseQueryParams(location) {
- var params = {};
- var query = unescape(location.search.substring(1));
- var vars = query.split('&');
- for (var i = 0; i < vars.length; i++) {
- var pair = vars[i].split('=');
- params[pair[0]] = pair[1];
- }
- return params;
- }
-
- /**
- * Creates a new URL by appending or replacing the given query key and value.
- * Not supporting URL with username and password.
- * @param {Location} location The original URL.
- * @param {string} key The query parameter name.
- * @param {string} value The query parameter value.
- * @return {string} The constructed new URL.
- */
- function setQueryParam(location, key, value) {
- var query = parseQueryParams(location);
- query[encodeURIComponent(key)] = encodeURIComponent(value);
-
- var newQuery = '';
- for (var q in query) {
- newQuery += (newQuery ? '&' : '?') + q + '=' + query[q];
- }
-
- return location.origin + location.pathname + newQuery + location.hash;
- }
-
- /**
- * @param {Node} el A node to search for ancestors with |className|.
- * @param {string} className A class to search for.
- * @return {Element} A node with class of |className| or null if none is found.
- */
- function findAncestorByClass(el, className) {
- return /** @type {Element} */(findAncestor(el, function(el) {
- return el.classList && el.classList.contains(className);
- }));
- }
-
- /**
- * Return the first ancestor for which the {@code predicate} returns true.
- * @param {Node} node The node to check.
- * @param {function(Node):boolean} predicate The function that tests the
- * nodes.
- * @return {Node} The found ancestor or null if not found.
- */
- function findAncestor(node, predicate) {
- var last = false;
- while (node != null && !(last = predicate(node))) {
- node = node.parentNode;
- }
- return last ? node : null;
- }
-
- function swapDomNodes(a, b) {
- var afterA = a.nextSibling;
- if (afterA == b) {
- swapDomNodes(b, a);
- return;
- }
- var aParent = a.parentNode;
- b.parentNode.replaceChild(a, b);
- aParent.insertBefore(b, afterA);
- }
-
- /**
- * Disables text selection and dragging, with optional whitelist callbacks.
- * @param {function(Event):boolean=} opt_allowSelectStart Unless this function
- * is defined and returns true, the onselectionstart event will be
- * surpressed.
- * @param {function(Event):boolean=} opt_allowDragStart Unless this function
- * is defined and returns true, the ondragstart event will be surpressed.
- */
- function disableTextSelectAndDrag(opt_allowSelectStart, opt_allowDragStart) {
- // Disable text selection.
- document.onselectstart = function(e) {
- if (!(opt_allowSelectStart && opt_allowSelectStart.call(this, e)))
- e.preventDefault();
- };
-
- // Disable dragging.
- document.ondragstart = function(e) {
- if (!(opt_allowDragStart && opt_allowDragStart.call(this, e)))
- e.preventDefault();
- };
- }
-
- /**
- * TODO(dbeam): DO NOT USE. THIS IS DEPRECATED. Use an action-link instead.
- * Call this to stop clicks on <a href="#"> links from scrolling to the top of
- * the page (and possibly showing a # in the link).
- */
- function preventDefaultOnPoundLinkClicks() {
- document.addEventListener('click', function(e) {
- var anchor = findAncestor(/** @type {Node} */(e.target), function(el) {
- return el.tagName == 'A';
- });
- // Use getAttribute() to prevent URL normalization.
- if (anchor && anchor.getAttribute('href') == '#')
- e.preventDefault();
- });
- }
-
- /**
- * Check the directionality of the page.
- * @return {boolean} True if Chrome is running an RTL UI.
- */
- function isRTL() {
- return document.documentElement.dir == 'rtl';
- }
-
- /**
- * Get an element that's known to exist by its ID. We use this instead of just
- * calling getElementById and not checking the result because this lets us
- * satisfy the JSCompiler type system.
- * @param {string} id The identifier name.
- * @return {!HTMLElement} the Element.
- */
- function getRequiredElement(id) {
- return assertInstanceof($(id), HTMLElement,
- 'Missing required element: ' + id);
- }
-
- /**
- * Query an element that's known to exist by a selector. We use this instead of
- * just calling querySelector and not checking the result because this lets us
- * satisfy the JSCompiler type system.
- * @param {(!Document|!DocumentFragment|!Element)} context The context object
- * for querySelector.
- * @param {string} selectors CSS selectors to query the element.
- * @return {!HTMLElement} the Element.
- */
- function queryRequiredElement(context, selectors) {
- var element = context.querySelector(selectors);
- return assertInstanceof(element, HTMLElement,
- 'Missing required element: ' + selectors);
- }
-
- // Handle click on a link. If the link points to a chrome: or file: url, then
- // call into the browser to do the navigation.
- document.addEventListener('click', function(e) {
- if (e.defaultPrevented)
- return;
-
- var el = e.target;
- if (el.nodeType == Node.ELEMENT_NODE &&
- el.webkitMatchesSelector('A, A *')) {
- while (el.tagName != 'A') {
- el = el.parentElement;
- }
-
- if ((el.protocol == 'file:' || el.protocol == 'about:') &&
- (e.button == 0 || e.button == 1)) {
- chrome.send('navigateToUrl', [
- el.href,
- el.target,
- e.button,
- e.altKey,
- e.ctrlKey,
- e.metaKey,
- e.shiftKey
- ]);
- e.preventDefault();
- }
- }
- });
-
- /**
- * Creates a new URL which is the old URL with a GET param of key=value.
- * @param {string} url The base URL. There is not sanity checking on the URL so
- * it must be passed in a proper format.
- * @param {string} key The key of the param.
- * @param {string} value The value of the param.
- * @return {string} The new URL.
- */
- function appendParam(url, key, value) {
- var param = encodeURIComponent(key) + '=' + encodeURIComponent(value);
-
- if (url.indexOf('?') == -1)
- return url + '?' + param;
- return url + '&' + param;
- }
-
- /**
- * Creates a CSS -webkit-image-set for a favicon request.
- * @param {string} url The url for the favicon.
- * @param {number=} opt_size Optional preferred size of the favicon.
- * @param {string=} opt_type Optional type of favicon to request. Valid values
- * are 'favicon' and 'touch-icon'. Default is 'favicon'.
- * @return {string} -webkit-image-set for the favicon.
- */
- function getFaviconImageSet(url, opt_size, opt_type) {
- var size = opt_size || 16;
- var type = opt_type || 'favicon';
- return imageset(
- 'chrome://' + type + '/size/' + size + '@scalefactorx/' + url);
- }
-
- /**
- * Creates a new URL for a favicon request for the current device pixel ratio.
- * The URL must be updated when the user moves the browser to a screen with a
- * different device pixel ratio. Use getFaviconImageSet() for the updating to
- * occur automatically.
- * @param {string} url The url for the favicon.
- * @param {number=} opt_size Optional preferred size of the favicon.
- * @param {string=} opt_type Optional type of favicon to request. Valid values
- * are 'favicon' and 'touch-icon'. Default is 'favicon'.
- * @return {string} Updated URL for the favicon.
- */
- function getFaviconUrlForCurrentDevicePixelRatio(url, opt_size, opt_type) {
- var size = opt_size || 16;
- var type = opt_type || 'favicon';
- return 'chrome://' + type + '/size/' + size + '@' +
- window.devicePixelRatio + 'x/' + url;
- }
-
- /**
- * Creates an element of a specified type with a specified class name.
- * @param {string} type The node type.
- * @param {string} className The class name to use.
- * @return {Element} The created element.
- */
- function createElementWithClassName(type, className) {
- var elm = document.createElement(type);
- elm.className = className;
- return elm;
- }
-
- /**
- * webkitTransitionEnd does not always fire (e.g. when animation is aborted
- * or when no paint happens during the animation). This function sets up
- * a timer and emulate the event if it is not fired when the timer expires.
- * @param {!HTMLElement} el The element to watch for webkitTransitionEnd.
- * @param {number} timeOut The maximum wait time in milliseconds for the
- * webkitTransitionEnd to happen.
- */
- function ensureTransitionEndEvent(el, timeOut) {
- var fired = false;
- el.addEventListener('webkitTransitionEnd', function f(e) {
- el.removeEventListener('webkitTransitionEnd', f);
- fired = true;
- });
- window.setTimeout(function() {
- if (!fired)
- cr.dispatchSimpleEvent(el, 'webkitTransitionEnd', true);
- }, timeOut);
- }
-
- /**
- * Alias for document.scrollTop getter.
- * @param {!HTMLDocument} doc The document node where information will be
- * queried from.
- * @return {number} The Y document scroll offset.
- */
- function scrollTopForDocument(doc) {
- return doc.documentElement.scrollTop || doc.body.scrollTop;
- }
-
- /**
- * Alias for document.scrollTop setter.
- * @param {!HTMLDocument} doc The document node where information will be
- * queried from.
- * @param {number} value The target Y scroll offset.
- */
- function setScrollTopForDocument(doc, value) {
- doc.documentElement.scrollTop = doc.body.scrollTop = value;
- }
-
- /**
- * Alias for document.scrollLeft getter.
- * @param {!HTMLDocument} doc The document node where information will be
- * queried from.
- * @return {number} The X document scroll offset.
- */
- function scrollLeftForDocument(doc) {
- return doc.documentElement.scrollLeft || doc.body.scrollLeft;
- }
-
- /**
- * Alias for document.scrollLeft setter.
- * @param {!HTMLDocument} doc The document node where information will be
- * queried from.
- * @param {number} value The target X scroll offset.
- */
- function setScrollLeftForDocument(doc, value) {
- doc.documentElement.scrollLeft = doc.body.scrollLeft = value;
- }
-
- /**
- * Replaces '&', '<', '>', '"', and ''' characters with their HTML encoding.
- * @param {string} original The original string.
- * @return {string} The string with all the characters mentioned above replaced.
- */
- function HTMLEscape(original) {
- return original.replace(/&/g, '&')
- .replace(/</g, '<')
- .replace(/>/g, '>')
- .replace(/"/g, '"')
- .replace(/'/g, ''');
- }
-
- /**
- * Shortens the provided string (if necessary) to a string of length at most
- * |maxLength|.
- * @param {string} original The original string.
- * @param {number} maxLength The maximum length allowed for the string.
- * @return {string} The original string if its length does not exceed
- * |maxLength|. Otherwise the first |maxLength| - 1 characters with '...'
- * appended.
- */
- function elide(original, maxLength) {
- if (original.length <= maxLength)
- return original;
- return original.substring(0, maxLength - 1) + '\u2026';
- }
-